/**
 * @author nttdocomo
 */
define(function(require) {
	aries.klass("aries.util.KeyMap", aries.Class.extend({
		/**
		 * @cfg {String} eventName
		 * The event to listen for to pick up key events.
		 */
		eventName : 'keydown',

		/**
		 * Add a new binding to this KeyMap.
		 *
		 * Usage:
		 *
		 *     // Create a KeyMap
		 *     var map = new Ext.util.KeyMap(document, {
		 *         key: Ext.EventObject.ENTER,
		 *         fn: handleKey,
		 *         scope: this
		 *     });
		 *
		 *     //Add a new binding to the existing KeyMap later
		 *     map.addBinding({
		 *         key: 'abc',
		 *         shift: true,
		 *         fn: handleKey,
		 *         scope: this
		 *     });
		 *
		 * @param {Object/Object[]} binding A single KeyMap config or an array of configs.
		 * The following config object properties are supported:
		 * @param {String/Array} binding.key A single keycode or an array of keycodes to handle.
		 * @param {Boolean} binding.shift True to handle key only when shift is pressed,
		 * False to handle the keyonly when shift is not pressed (defaults to undefined).
		 * @param {Boolean} binding.ctrl True to handle key only when ctrl is pressed,
		 * False to handle the key only when ctrl is not pressed (defaults to undefined).
		 * @param {Boolean} binding.alt True to handle key only when alt is pressed,
		 * False to handle the key only when alt is not pressed (defaults to undefined).
		 * @param {Function} binding.handler The function to call when KeyMap finds the
		 * expected key combination.
		 * @param {Function} binding.fn Alias of handler (for backwards-compatibility).
		 * @param {Object} binding.scope The scope of the callback function.
		 * @param {String} binding.defaultEventAction A default action to apply to the event.
		 * Possible values are: stopEvent, stopPropagation, preventDefault. If no value is
		 * set no action is performed..
		 */
		addBinding : function(binding) {
			var keyCode = binding.key, processed = false, key, keys, keyString, i, len;

			if ($.isArray(binding)) {
				for ( i = 0, len = binding.length; i < len; i++) {
					this.addBinding(binding[i]);
				}
				return;
			}

			if (_.isString(keyCode)) {
				keys = [];
				keyString = keyCode.toUpperCase();

				for ( i = 0, len = keyString.length; i < len; ++i) {
					keys.push(keyString.charCodeAt(i));
				}
				keyCode = keys;
				processed = true;
			}

			if (!$.isArray(keyCode)) {
				keyCode = [keyCode];
			}

			if (!processed) {
				for ( i = 0, len = keyCode.length; i < len; ++i) {
					key = keyCode[i];
					if (_.isString(key)) {
						keyCode[i] = key.toUpperCase().charCodeAt(0);
					}
				}
			}

			this.bindings.push($.extend({
				keyCode : keyCode
			}, binding));
		},

		/**
		 * Check if the modifiers on the event match those on the binding
		 * @private
		 * @param {Object} binding
		 * @param {Ext.EventObject} event
		 * @return {Boolean} True if the event matches the binding
		 */
		checkModifiers : function(binding, e) {
			var keys = ['shift', 'ctrl', 'alt'], i = 0, len = keys.length, val, key;

			for (; i < len; ++i) {
				key = keys[i];
				val = binding[key];
				if (!(val === undefined || (val === e[key + 'Key']))) {
					return false;
				}
			}
			return true;
		},

		/**
		 * Disable this KeyMap
		 */
		disable : function() {
			var me = this;

			if (me.enabled) {
				me.target.off(me.eventName, me.handleKeyDown);
				me.enabled = false;
			}
		},

		initialize : function(config) {
			var me = this;
			_.bindAll(this,'handleKeyDown')

			// Handle legacy arg list in which the first argument is the target.
			// TODO: Deprecate in V5
			if ((arguments.length !== 1) || ( typeof config === 'string') || config.dom || config.tagName || config === document || config.isComponent) {
				me.legacyConstructor.apply(me, arguments);
				return;
			}

			$.extend(me, config);
			me.bindings = [];

			/*if (!me.target.isComponent) {
			 me.target = Ext.get(me.target);
			 }*/

			if (me.binding) {
				me.addBinding(me.binding);
			} else if (config.key) {
				me.addBinding(config);
			}
			me.enable();
		},

		/**
		 * Enables this KeyMap
		 */
		enable : function() {
			var me = this;

			if (!me.enabled) {
				me.target.on(me.eventName, me.handleKeyDown);
				me.enabled = true;
			}
		},

		/**
		 * Process any keydown events on the element
		 * @private
		 * @param {Ext.EventObject} event
		 */
		handleKeyDown : function(event) {
			var me = this, bindings, i, len;

			if (this.enabled) {//just in case
				bindings = this.bindings;
				i = 0;
				len = bindings.length;

				// Process the event
				event = me.processEvent.apply(me || me.processEventScope, arguments);

				// If the processor does not return a keyEvent, we can't process it.
				// Allow them to return false to cancel processing of the event
				if (!event.getKey) {
					return event;
				}
				for (; i < len; ++i) {
					this.processBinding(bindings[i], event);
				}
			}
		},

		/**
		 * Process a particular binding and fire the handler if necessary.
		 * @private
		 * @param {Object} binding The binding information
		 * @param {Ext.EventObject} event
		 */
		processBinding : function(binding, event) {
			if (this.checkModifiers(binding, event)) {
				var key = event.getKey(), handler = binding.fn || binding.handler, scope = binding.scope || this, keyCode = binding.keyCode, defaultEventAction = binding.defaultEventAction, i, len, keydownEvent = event;

				for ( i = 0, len = keyCode.length; i < len; ++i) {
					if (key === keyCode[i]) {
						if (handler.call(scope, key, event) !== true && defaultEventAction) {
							keydownEvent[defaultEventAction]();
						}
						break;
					}
				}
			}
		},

		/**
		 * @cfg {Function} processEvent
		 * An optional event processor function which accepts the argument list provided by the
		 * {@link #eventName configured event} of the {@link #target}, and returns a keyEvent for processing by the KeyMap.
		 *
		 * This may be useful when the {@link #target} is a Component with s complex event signature. Extra information from
		 * the event arguments may be injected into the event for use by the handler functions before returning it.
		 */
		processEvent : function(event) {
			return event;
		}
	}))
})
